import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Vector;


public class ChatServer {
	//	ArrayList<SessionPair> sessions = new ArrayList<SessionPair>();
	Vector<ClientListener> clients = new Vector<ClientListener>();

	public ChatServer(){
		try {
			ServerSocket sSocket = new ServerSocket(5555);
			while(true){
				System.out.println("waiting for new connections ("+sSocket.getLocalPort()+")");
				Socket serverSocket = sSocket.accept();
				System.out.println("Accepted client " + serverSocket.getInetAddress()+" on port "+serverSocket.getPort());
				ClientListener connect = new ClientListener(serverSocket);
				new Thread(connect).start();
				clients.add(connect);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new ChatServer();
	}

	private class ClientListener implements Runnable {
		String userName;
		Socket connection = null;
		BufferedReader read;
		PrintWriter write;
		ClientListener other;
		int port;

		public ClientListener(Socket serverSocket) {
			this.connection = serverSocket;
		}

		public void setother(ClientListener other) throws IOException{
			this.other = other;
		}

		public void run() {
			try {
				int port = connection.getPort();
				new PrintWriter(connection.getOutputStream(),true).println(port);
				InetAddress inetaddr = connection.getInetAddress();
				//System.out.println("waiting for con close");
				//while(!connection.isClosed()){ }
				long timeout = System.currentTimeMillis()+10000;
				connection = null;
				while(connection==null){
					if(System.currentTimeMillis() > timeout){
						System.err.println("Connection timeout (User failed to connect)");
						return;
					}
					try{
						connection = new Socket(inetaddr, port);
					}catch(IOException e){

					}
				}
				read = new BufferedReader(new InputStreamReader(connection.getInputStream()));
				write = new PrintWriter(connection.getOutputStream(), true);
				while(!read.ready()){ }
				String user = read.readLine();
				for(ClientListener s: clients){
					if(s!=this){
						if(s.userName.equals(user)){
							write.println("Username not available");
							clients.remove(this);
							connection.close();
                            return;
						}
					}
				}
				write.println("Username available");
				userName = user;
				printActions();
				while(other==null || other.connection == null){
					try{
						write.println("What client do you want to connect to?");
						String client = readLineStopWhenConnected();
						if(client==null){
							break;
						}
						if(!checkActions(client)){
							for(ClientListener s: clients){
								if(s.userName.equals(client)){
									setother(s);
									s.setother(this);
									break;
								}
							}
							if(other==null || other.connection == null){
								write.println("User not connected to server");
							}
						}
					}catch(UnknownHostException e){
						write.println("Not a valid IP address");
					}
					catch(IOException e){
						System.out.println("IOException");
					}
				}
				if(clients.remove(this)){
					System.out.println("Removed "+userName+" from client list");
				}
				if(connection.isClosed()){
					return;
				}
				write.println("Chat started with "+other.userName);
				System.out.println("Chat started between "+userName+" and "+other.userName);
				read = new BufferedReader(new InputStreamReader(connection.getInputStream()));
				write = new PrintWriter(other.connection.getOutputStream(), true);
				while(true){
					String line = read.readLine();
					if(line==null){
						other.connection.close();
						connection.close();
						System.out.println("Connection closed by "+connection.getInetAddress());
						break;
					}
					checkActions(line);
					System.out.println("read \'" + line + "\' from " + connection.getInetAddress());
					write.println(line);
					System.out.println("wrote \'" + line + "\' to " + other.connection.getInetAddress());
				}
			} catch (IOException e) {
				System.err.println("Session between "+ userName+" and "+ other.userName +" closed");
				// TODO Auto-generated catch block
				//e.printStackTrace();
			}
		}

		private boolean checkActions(String client) {
			if(client.equalsIgnoreCase("exit")){
				try {
					connection.close();

				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				return true;
			}
			if(client.equalsIgnoreCase("users")){
				listAvailableClients();
				return true;
			}
			if(client.equalsIgnoreCase("actions")){
				printActions();
				return true;
			}
			return false;
		}

		private void printActions() {
			write.println("Available actions:");
			write.print("users, ");
			write.print("exit, ");
			write.print("actions\n");
		}

		boolean a,b,c,d;

		private String readLineStopWhenConnected() throws IOException {
			boolean a=connection.isClosed();
			boolean b=connection.isConnected();
			boolean c=connection.getRemoteSocketAddress()==null;
			boolean d=connection.isInputShutdown();
			while(!read.ready()){
				boolean a1=connection.isClosed();
				boolean b1=connection.isConnected();
				boolean c1=connection.getRemoteSocketAddress()==null;
				boolean d1=connection.isInputShutdown();
				if(a1!=a || b1!=b || c1!=c || d1!=d){
					System.out.println("Connection changed");
				}
				a = a1; b=b1; c=c1; d=d1;
				if((other!=null && other.connection != null) || connection.isClosed()){
					return null;
				}
			}
			return read.readLine();
		}

		private void listAvailableClients(){
			write.println("Available users:");
			for(ClientListener c : clients){
				if(c!=this){
					write.println(c.userName);
				}
			}
		}

	}

}
